/*
 * @file iso9660_init.c
 *
 *    ISO9660 directory operation table.
 */


#if HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <dirent.h>
#include <rtems/seterr.h>

#include "iso9660.h"
#include "iso9660_handlers.h"
#include "iso9660_io.h"
#include "iso9660_record.h"


/*
 *   Handler table for iso9660 directory nodes
 */
const rtems_filesystem_file_handlers_r ISO9660_directory_handlers = {
  rtems_iso9660_dir_open,
  rtems_iso9660_dir_close,
  rtems_iso9660_dir_read,
  rtems_iso9660_dir_write,
  rtems_filesystem_default_ioctl,
  rtems_filesystem_default_lseek,
  rtems_iso9660_dir_fstat,
  rtems_filesystem_default_fchmod,
  rtems_filesystem_default_ftruncate,
  rtems_filesystem_default_fpathconf,
  rtems_filesystem_default_fsync,
  rtems_filesystem_default_fdatasync,
  rtems_filesystem_default_fcntl,
  rtems_filesystem_default_rmnod
};

int
iso9660_lookup_node_in_dir_record(
    iso9660_fs_info_t *fs,
    const char       *node,
    int                node_len,
    iso9660_record_t  *record,
    iso9660_record_t  *result
)
{
  iso9660_record_t lookup;
  int rc, offset = 0;

  rc = iso9660_record_open(record);
  if (rc < 0)
    rtems_iso9660_error_and_return_minus_one(errno,
        "lookup_node: Cannot open record.");

  /* Directories have always at least 2 entries : '.' and '..' */
  offset = iso9660_record_next_entry(record, offset, &lookup, 0);

  while (offset > 0)
  {
      if ((lookup.name_len == node_len) && !strncmp(lookup.name, node, node_len))
      {
          iso9660_record_copy(result, &lookup);
          iso9660_record_free(&lookup);
          iso9660_record_close(record);

          return 0;
      }

      iso9660_record_free(&lookup);
      offset = iso9660_record_next_entry(record, offset, &lookup, 0);
  }

  iso9660_record_close(record);
  rtems_iso9660_error_and_return_minus_one(ENOENT,
      "lookup_node: Node not found.");
}


/**
 *  This method defines the interface to the default open(2)
 *  system call support which is provided by a file system
 *  implementation.
 */
int
rtems_iso9660_dir_open(
    rtems_libio_t   *iop,
    const char     *pathname,
    uint32_t         flag,
    uint32_t         mode
)
{
  iso9660_record_t   *record = rtems_iso9660_get_record_iop(iop);
  int                 rc;

  if (!record->flags.directory)
    rtems_iso9660_error_and_return_minus_one(ENOTDIR,
        "dir_open: Given record is not a directory.");

  rc = iso9660_record_open(record);
  iop->offset = 0;

  return rc;
}


/**
 *  This method defines the interface to the default close(2)
 *  system call support which is provided by a file system
 *  implementation.
 */
int
rtems_iso9660_dir_close(
    rtems_libio_t *iop
)
{
  iso9660_record_t   *record = rtems_iso9660_get_record_iop(iop);

  iso9660_record_close(record);

  return 0;
}


ssize_t
rtems_iso9660_dir_read(
    rtems_libio_t   *iop,
    void            *buffer,
    size_t           count
)
{
  iso9660_record_t   *record = rtems_iso9660_get_record_iop(iop);
  iso9660_record_t   record_entry;
  struct dirent     *entry, *last_entry;
  int                 bytes = 0, rc;

  /* Avoid problems with count not multiple of sizeof(struct dirent) */
  last_entry = (struct dirent*)buffer + (count / sizeof(struct dirent));

  for (entry = buffer; entry < last_entry; entry += sizeof(struct dirent)) {
    memset(entry, 0, sizeof(struct dirent));
    rc = iso9660_record_next_entry(record, iop->offset, &record_entry,
          ISO9660_RECORD_SKIP_PARENT | ISO9660_RECORD_SKIP_CURRENT);

    if (rc == -1)
      rtems_iso9660_error_and_return_minus_one(errno,
          "dir_read: Failed to fetch next entry.");

    /* Directory end */
    if (rc == 0)
      return 0;

    entry->d_ino = record_entry.location;
    entry->d_off = iop->offset;
    entry->d_reclen = sizeof(struct dirent);
    entry->d_namlen = record_entry.name_len;

    memcpy(entry->d_name, record_entry.name, entry->d_namlen);
    if (entry->d_namlen < sizeof(entry->d_name))
      entry->d_name[entry->d_namlen] = '\0';

    iso9660_record_free(&record_entry);
    bytes += sizeof(struct dirent);
    iop->offset = rc;
  }

  return bytes;
}


ssize_t
rtems_iso9660_dir_write(
    rtems_libio_t   *iop,
    const void     *buffer,
    size_t           count
)
{
  rtems_set_errno_and_return_minus_one(ENOSYS);
}


int
rtems_iso9660_dir_fstat(
    rtems_filesystem_location_info_t  *pathloc,
    struct stat*                      buf
)
{
  iso9660_record_t   *record = rtems_iso9660_get_record_pathloc(pathloc);

  if (record == NULL)
    rtems_iso9660_error_and_return_minus_one(ENOENT,
        "file_fstat: Failed to retrieve record from pathloc.");

  return iso9660_record_stat(record, buf, 1);
}

#if 0
/**
 *  This method defines the interface to the default read(2)
 *  system call support which is provided by a file system
 *  implementation.
 */
ssize_t iso9660_dir_read(
  rtems_libio_t *iop,
  void          *buffer,
  size_t         count
);

/**
 *  This method defines the interface to the default write(2) system call
 *  support which is provided by a file system implementation.
 */
ssize_t iso9660_dir_write(
  rtems_libio_t *iop,
  const void    *buffer,
  size_t         count
);

/**
 *  This method defines the interface to the default ioctl(2) system call
 *  support which is provided by a file system implementation.
 */
int iso9660_dir_ioctl(
  rtems_libio_t *iop,
  uint32_t       command,
  void          *buffer
);

/**
 *  This method defines the interface to the default lseek(2) system call
 *  support which is provided by a file system implementation.
 */
rtems_off64_t iso9660_dir_lseek(
  rtems_libio_t *iop,
  rtems_off64_t  length,
  int            whence
);

/**
 *  This method defines the interface to the default fstat(2) system call
 *  support which is provided by a file system implementation.
 */
int iso9660_dir_fstat(
  rtems_filesystem_location_info_t *loc,
  struct stat                      *buf
);

/**
 *  This method defines the interface to the default fchmod(2) system call
 *  support which is provided by a file system implementation.
 */
int iso9660_dir_fchmod(
  rtems_filesystem_location_info_t *loc,
  mode_t                            mode
);

/**
 *  This method defines the interface to the default ftruncate(2) system call
 *  support which is provided by a file system implementation.
 */
int iso9660_dir_ftruncate(
  rtems_libio_t *iop,
  rtems_off64_t  length
);

/**
 *  This method defines the interface to the default fpathconf(2) system call
 *  support which is provided by a file system implementation.
 */
int iso9660_dir_fpathconf(
  rtems_libio_t *iop,
  int name
);

/**
 *  This method defines the interface to the default fsync(2) system call
 *  support which is provided by a file system implementation.
 */
int iso9660_dir_fsync(
  rtems_libio_t *iop
);

/**
 *  This method defines the interface to the default fdatasync(2) system call
 *  support which is provided by a file system implementation.
 */
int iso9660_dir_fdatasync(
  rtems_libio_t *iop
);

/**
 *  This method defines the interface to the default fnctl(2) system call
 *  support which is provided by a file system implementation.
 */
int iso9660_dir_fcntl(
  int            cmd,
  rtems_libio_t *iop
);

/**
 *  This method defines the interface to the default rmnod(2) system call
 *  support which is provided by a file system implementation.
 */
int iso9660_dir_rmnod(
 rtems_filesystem_location_info_t      *parent_loc,   /* IN */
 rtems_filesystem_location_info_t      *pathloc       /* IN */
);
#endif


